
/**
 * Java里方法的参数传递方式只有一种：值传递。
 * 所谓值传递，就是将实际参数值的副本（复制品）传入方法内，而参数本身不会受到任何影响。
 */
class PrimitiveTransfer {
    public static void swap(int a, int b) {
        int tmp = a;
        a = b;
        b = tmp;
        System.out.println("swap方法里，a的值是" + a + ",b的值是" + b);
    }

    public static void main() {
        int a = 6;
        int b = 9;
        // 传入swap()方法的只是a、b的副本，而不是a、b本身，
        PrimitiveTransfer.swap(a, b);
        System.out.println("交换结束后，变量a的值是" + a + ",变量b的值是" + b);
    }
}

/**
 * 前面看到的是基本类型的参数传递，Java对于引用类型的参数传递，一样采用的是值传递方式。
 */
class DataSwap {
    int a;
    int b;
}

class ReferenceTransfer {
    public static void swap(DataSwap dw) {
        int tmp = dw.a;
        dw.a = dw.b;
        dw.b = tmp;
        System.out.println("swap方法里，a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
    }

    public static void swap2(DataSwap dw) {
        int tmp = dw.a;
        dw.a = dw.b;
        dw.b = tmp;
        System.out.println("swap方法里，a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
        dw = null;
    }

    public static void main() {
        DataSwap dw = new DataSwap();
        dw.a = 6;
        dw.b = 9;
        ReferenceTransfer.swap(dw);
        System.out.println("交换结束后，a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
        /*
         * 从上面运行结果来看，在swap()方法里，a、b两个成员变量的值被交换成功。
         * 不仅如此，当swap()方法执行结束后，main()方法里a、b两个成员变量的值也被交换了。
         * 这很容易造成一种错觉：调用swap()方法时，传入swap()方法的就是dw对象本身，而不是它的复制品。
         * 但这只是一种错觉.
         *
         * 为了更好地证明main()方法中的dw和swap()方法中的dw是两个变量，
         * 在swap()方法的最后一行增加如下代码：
         * dw = null;
         * 使用swap2演示
         */
        dw.a = 6;
        dw.b = 9;
        ReferenceTransfer.swap2(dw);
        System.out.println("交换结束后，a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
        /*
         * 执行上面代码的结果是swap2()方法中的dw变量不再指向任何有效内存，程序其他地方不做任何修改。
         * main()方法调用了swap()方法后，再次访问dw变量的a、b两个成员变量，依然可以输出9、6。
         * 可见main()方法中的dw变量没有受到任何影响。
         * 实际上，当swap()方法中增加dw=null;代码后，swap()方法中失去了DataWrap的引用，不可再访问堆内存中的DataWraper对象。
         * 但main()方法中的dw变量不受任何影响，依然引用DataWrap对象，所以依然可以输出DataWrap对象的a、b成员变量的值。
         */
    }
}

/**
 * 从JDK 1.5之后，Java允许定义形参个数可变的参数，从而允许为方法指定数量不确定的形参。
 * 如果在定义方法时，在最后一个形参的类型后增加三点（...），则表明该形参可以接受多个参数值，多个参数值被当成数组传入。
 */
class Varargs {
    public static void test(int a, String... books) {
        // books被当成数组处理
        for (String tmp : books) {
            System.out.println(tmp);
        }
        System.out.println(a);
    }

    public static void test2(int a, String[] books) {
        for (String tmp : books) {
            System.out.println(tmp);
        }
        System.out.println(a);
    }

    public static void main() {
        Varargs.test(5, "学习Java", "Java类和对象");
        /*
         * 从上面运行结果可以看出，当调用test()方法时，books参数可以传入多个字符串作为参数值。
         * 从test()的方法体代码来看，形参个数可变的参数本质就是一个数组参数，也就是说，下面两个方法签名的效果完全一样。
         *     public static void test(int a, String... books);
         * 下面采用数组形式来定义方法
         *     public static void test(int a, String[] books);
         *
         * 这两种形式都包含了一个名为books的形参，在两个方法的方法体内都可以把books当成数组处理。
         * 但区别是调用两个方法时存在差别，对于以可变形参的形式定义的方法，调用方法时更加简洁
         */
        Varargs.test(5, "学习Java", "Java类和对象");
        // 传给books参数的实参数值无须是一个数组，但如果采用数组形参来声明方法，调用时则必须传给该形参一个数组，
        Varargs.test2(5, new String[]{"学习Java", "Java类和对象"});
        /*
         * 对比两种调用test()方法的代码，明显第一种形式更加简洁。
         * 实际上，即使是采用形参个数可变的形式来定义方法，调用该方法时也一样可以为个数可变的形参传入一个数组。
         */
        Varargs.test(5, new String[]{"学习Java", "Java类和对象"});
        /*
         * 最后还要指出的是，数组形式的形参可以处于形参列表的任意位置，但个数可变的形参只能处于形参列表的最后。
         * 也就是说，一个方法中最多只能有一个个数可变的形参。
         */
    }
}

/**
 * 一个方法体内调用它自身，被称为方法递归。
 * 方法递归包含了一种隐式的循环，它会重复执行某段代码，但这种重复执行无须循环控制。
 */
class Recursive {
    /**
     * 例如有如下数学题。已知有一个数列：f(0)=1，f(1)=4，f(n+2)=2*f(n+1)+f(n)，其中n是大于0的整数，求f(10)的值。
     * 这个题可以使用递归来求得。下面程序将定义一个fn方法，用于计算f(10)的值。
     *
     * @param n x值
     * @return y值
     */
    public static int fn(int n) {
        if (n == 0) {
            return 1;
        } else if (n == 1) {
            return 4;
        } else {
            return 2 * fn(n - 1) + fn(n - 2);
        }
    }
    /*
     * 仔细看上面递归的过程，当一个方法不断地调用它本身时，必须在某个时刻方法的返回值是确定的，即不再调用它本身，
     * 否则这种递归就变成了无穷递归，类似于死循环。
     * 因此定义递归方法时有一条最重要的规定：递归一定要向已知方向递归。
     */

    /**
     * 例如，如果把上面数学题改为如此。
     * 已知有一个数列：f(20)=1，f(21)=4，f(n+2)=2*f(n+1)+f(n)，其中n是大于0的整数，求f(10)的值。
     * 那么fn的方法体就应该改为如下：
     */
    public static int fn2(int n) {
        if (n == 20) {
            return 1;
        } else if (n == 21) {
            return 4;
        } else {
            return 2 * fn2(n + 2) + fn2(n + 1);
        }
    }

    /*
     * 对于求fn(10)而言，如果fn(0)和fn(1)是已知的，则应该采用fn(n)=2*fn(n-1)+fn(n-2)的形式递归，因为小的一端已知；
     * 如果fn(20)和fn(21)是已知的，则应该采用fn(n)=fn(n+2)-2*fn(n+1)的形式递归，因为大的一端已知。
     */
    public static void main() {
        System.out.println(Recursive.fn(10));
    }
}

/**
 * Java允许同一个类里定义多个同名方法，只要形参列表不同就行。
 * 如果同一个类中包含了两个或两个以上方法的方法名相同，但形参列表不同，则被称为方法重载。
 * <p>
 * 从上面介绍可以看出，在Java程序中确定一个方法需要三个要素
 * ➢ 调用者，也就是方法的所属者，既可以是类，也可以是对象。
 * ➢ 方法名，方法的标识。
 * ➢ 形参列表，当调用方法时，系统将会根据传入的实参列表匹配。
 * <p>
 * 方法重载的要求就是两同一不同：同一个类中方法名相同，参数列表不同。
 * 至于方法的其他部分，如方法返回值类型、修饰符等，与方法重载没有任何关系。
 */
class Overload {
    public void test() {
        System.out.println("无参数");
    }

    public void test(String msg) {
        System.out.println("重载的test方法 " + msg);
    }

    public static void main() {
        Overload ol = new Overload();
        ol.test();
        ol.test("hello");
    }
}

/**
 * 不仅如此，如果被重载的方法里包含了个数可变的形参，则需要注意。
 * 看下面程序里定义的两个重载的方法。
 */
class OverloadVarargs {
    public void test(String msg) {
        System.out.println("只有一个字符串参数的test方法");
    }

    /**
     * 因为前面已经有了一个test方法，test方法里有一个字符串参数
     * 此处的个数可变形参里不包含一个字符串参数的形式
     */
    public void test(String... books) {
        System.out.println("******形参个数可变的test方法，形参个数为：" + books.length + "*****");
    }

    /**
     * 结论：当多个方法同时满足条件时，优先调用非可变参数的方法。
     *
     * 大部分时候并不推荐重载形参个数可变的方法，因为这样做确实没有太大的意义，而且容易降低程序的可读性。
     */
    public static void main() {
        OverloadVarargs olv = new OverloadVarargs();
        olv.test(); // 调用第2个test方法
        olv.test("aa"); // 调用第1个test方法
        olv.test("aa", "bb"); // 调用第2个test方法
        olv.test(new String[]{"aa"}); // 调用第2个test方法
    }
}

public class ClassMethod {
    public static void main(String[] args) {
        PrimitiveTransfer.main();
        ReferenceTransfer.main();
        Varargs.main();
        Recursive.main();
        Overload.main();
        OverloadVarargs.main();
    }
}
